#ifndef __CLX_SERVICE_HPP__
#define __CLX_SERVICE_HPP__

#include "util.hpp"
#include "httplib.h"
#include "data.hpp"
#include "config.hpp"

extern clx_cloud::DataManager *_data;
namespace clx_cloud {
    class Service {
        // 搭建http服务器，并进行业务处理
        private:
            int _server_port;             
            std::string _server_ip;
            std::string _download_prefix;
            httplib::Server _server;
        public:
            Service() {
                Config *config = Config::GetInstance();
                _server_port = config->GetServerPort();
                _server_ip = config->GetServerIp();
                _download_prefix = config->GetDownloadPrefix();
            }

            // 上传请求处理函数
            static void Upload(const httplib::Request &req, httplib::Response &rsp) {
                // post /upload   文件数据在正文 (正文并非全是文件数据)
                auto ret = req.has_file("file");  // 判断有没有上传的文件区域
                if (ret == false) {
                    rsp.status = 400;
                    return;
                }
                const auto& file = req.get_file_value("file");
                std::string back_dir = Config::GetInstance()->GetBackDir();
                std::string realpath = back_dir + FileUtil(file.filename).FileName();
                FileUtil fu(realpath);
                fu.SetContent(&(file.content));  // 将数据写入文件中
                BackupInfo info;
                info.NewBackupInfo(realpath);    // 组织备份文件信息
                _data->Insert(info); 
                return;
            }  

            static std::string TimetoStr(time_t t) {
                std::string tmp = std::ctime(&t);
                return tmp;
            }
            // 展示页面获取请求
            static void ListShow(const httplib::Request &req, httplib::Response &rsp) { 
                // 1. 获取所有文件的备份信息
                std::vector<BackupInfo> array;
                _data->GetAll(&array);
                // 2. 根据所有备份信息，组织html数据
                std::stringstream ss;
                ss << "<html><head><title>Download</title></head>";
                ss << "<body><h1>Download</h1><table>";
                for (auto &a : array) {
                    ss << "<tr>";
                    std::string filename = FileUtil(a.real_path).FileName();
                    ss << "<td><a href='" << a.url_path << "'>" << filename << "</a></td>";
                    ss << "<td align='right'>" << TimetoStr(a.mtime) << "</td>";
                    ss << "<td align='right'>" << a.fsize / 1024 << "k</td>";
                    ss << "</tr>";
                }
                ss << "</table></body></html>";
                rsp.body = ss.str();
                rsp.set_header("Content-Type", "text/html");
                rsp.status = 200;
                return; 
            } 

            static std::string GetETag(const BackupInfo &info) {
                // etg: filename-fsze-mtime
                FileUtil fu(info.real_path);
                std::string etag = fu.FileName();
                etag += "-";
                etag += std::to_string(info.fsize);
                etag += "-";
                etag += std::to_string(info.mtime);
                return etag;
            }
            // 文件下载处理函数
            static void Download(const httplib::Request &req, httplib::Response &rsp) { 
                // 1、获取客户端请求的资源路径path
                // 2、根据资源路径，获取文件备份信息
                BackupInfo info;
                _data->GetOneByUrl(req.path, &info);
                // 3、判断文件是否被压缩，如果被压缩，就先进行解压缩
                if (info.pack_flag == true) {
                    FileUtil fu(info.pack_path);
                    fu.UnCompress(info.real_path); // 将文件解压到备份路径下
                    // 4、删除压缩包，修改备份信息
                    fu.Remove();
                    info.pack_flag = false;
                    _data->Update(info);
                }

                FileUtil fu(info.real_path);

                // 5、如果没有If-Range字段则是正常下载，如果有但是etag值不一致也是正常下载
                // If-Range字段存在，并且etag值一致则是断点续传
                bool retrans = false;
                std::string old_etag;
                if (req.has_header("If-Range")) {
                    old_etag = req.get_header_value("If-Range");
                    // 有If-Range字段且，这个字段的值与请求文件的最新etag一致则符合断点续传
                    if (old_etag == GetETag(info)) {
                        retrans = true;
                    }
                }

                // 6、设置响应同步字段 Etag Accept-Ranges: bytes
                if (retrans == false) {

                    // 7、读取文件数据，放入rsp.body中
                    fu.GetContent(&rsp.body);
                    rsp.set_header("Accept-Ranges", "bytes");
                    rsp.set_header("ETag", GetETag(info));
                    rsp.set_header("Content-Type", "application/octet-stream");
                    rsp.status = 200;

                } else {
                    // 7、读取文件数据，放入rsp.body中
                    // httplib 内部实现了对于区间请求也就是断点续传请求的处理
                    // 只需要用户将文件所有数据读取到rsp.body中，它内部会自动根据请求区间，从body中取出指定区间数据进行响应

                    // httplib 发现Accept-Rages 默认帮助我们设置
                    // std::string range = req.get_header_val("Range"); bytes = start-end 类似这样的字符串
                    // rsp.set_header("Content-Range", "bytes start-end/fsize");

                    fu.GetContent(&rsp.body);
                    rsp.set_header("Accept-Ranges", "bytes");
                    rsp.set_header("ETag", GetETag(info));
                    rsp.set_header("Content-Type", "application/octet-stream");

                    // 区间请求响应的状态码是206
                    rsp.status = 206;
                }
            } 

            bool RunModule() {
                _server.Post("/upload", Upload);
                _server.Get("/listshow", ListShow);
                _server.Get("/", ListShow);
                std::string download_url = _download_prefix + "(.*)";
                _server.Get(download_url, Download);
                _server.listen("0.0.0.0", 8888);
                std::cout << _server_ip << ":" << _server_port << std::endl;
                return true;
            }
    };
}

#endif